
// Created by 29019 on 2020/4/18.
//
#define _WSPIAPI_H_
#define _WINSOCKAPI_
#include "tcp_client.h"
#include <stdio.h>
#include <cstring>
#include <string.h>
#include <chrono>

#include "event2/bufferevent.h"
#include "event2/bufferevent_struct.h"
#include "event2/buffer.h"
#include "event2/listener.h"
#include "event2/util.h"
#include "event2/event.h"
#include "event2/thread.h"
/* For int types. */
#include <event2/util.h>
/* For struct event */
#include <event2/event_struct.h>

using namespace std::chrono;


class ClientCallback{
public:
  static void conn_writecb(struct bufferevent *, void *);
  static void conn_readcb(struct bufferevent *, void *);
  static void conn_eventcb(struct bufferevent *, short, void *);

};


void delay(int ms);
int ThreadRun(TcpClientLibevent *p);


void ClientCallback::conn_writecb(struct bufferevent *bev, void *user_data)
{

}

int ThreadRun(TcpClientLibevent *p) {
  if (nullptr != p) {
    while (true)
    {
      if ((p->mStatus == TcpClientLibevent::STOP)){
          Sleep(100);
          continue;
      }
      if ((p->mStatus == TcpClientLibevent::FAIL) ||
          (p->mStatus == TcpClientLibevent::UNCONNECTED)){ 
          
          p->ConnectServer();
#ifdef _WIN32
          Sleep(100);
#else
          //todo linux?��sleep
#endif
      }
      int ret = p->Dispatch();
      if(ret < 0){
          break;
      }
    }
  }
  return 0;
}

void conn_readcb(struct bufferevent *bev, void *user_data)
{
    TcpClientLibevent *client = (TcpClientLibevent*)user_data;
    struct evbuffer *input = bufferevent_get_input(bev);
    size_t sz = evbuffer_get_length(input);
    if (sz > 0)
    {
        uint8_t *msg = new uint8_t[sz];
        int ret = bufferevent_read(bev, msg, sz);
        printf("%s\n", msg);
        if(client->mObserver != nullptr){
			client->mObserver->OnData(msg, ret);
        }
        delete[] msg;
    }
}

void ClientCallback::conn_eventcb(struct bufferevent *bev, short events, void *user_data)
{
  TcpClientLibevent *p;
  p = (TcpClientLibevent *)user_data;
  if (p == nullptr) {
      return;
  }
  if (events & BEV_EVENT_EOF) {
    std::cout<<"BEV_EVENT_EOF"<<std::endl;
    if (nullptr != p->mObserver)
        p->mObserver->OnDisConnected("BEV_EVENT_EOF");
    if (p != nullptr)
        p->mStatus = TcpClientLibevent::UNCONNECTED;
    printf("Connection closed\n");
  }
  else if (events & BEV_EVENT_ERROR) {
    printf("Got an error on the connection: %s %d\n", strerror(errno),errno);
    if (nullptr != p->mObserver)
        p->mObserver->OnDisConnected("BEV_EVENT_ERROR");
    p->mStatus = TcpClientLibevent::FAIL;
}
  else if (events & BEV_EVENT_CONNECTED) {
      p->mSocketFD  = (uint64_t)event_get_fd(&(bev->ev_read));
      std::cout << "socket fd  " << p->mSocketFD << std::endl;
      if (nullptr != p->mObserver)
          p->mObserver->OnConnected();
  p->mStatus = TcpClientLibevent::CONNECTED;
      return;
  }
  else if(events & BEV_EVENT_TIMEOUT){
      std::cout<<"Got an timeout event on Connection"<<std::endl;

  }
  bufferevent_free(bev);
}

void delay(int ms)
{
    clock_t start = clock();
    while (clock() - start < ms);
}

bool TcpClientLibevent::Connected() {
    return ((mStatus == CONNECTED)?true:false);
}
TcpClientLibevent::~TcpClientLibevent() {
    event_base_free(mBase);
};

TcpClientLibevent::TcpClientLibevent(std::string addrinfo, int port, TcpClientLibevent::TcpClientObserver *p) :
	mStatus(UNCONNECTED),
	mObserver(nullptr),
    mBev(nullptr)
{
        memset(&mSrv, 0, sizeof(mSrv));
#ifdef linux
        mSrv.sin_addr.s_addr = inet_addr(addrinfo.c_str());
        mSrv.sin_family = AF_INET;
#endif
#ifdef _WIN32
        mSrv.sin_addr.S_un.S_addr = inet_addr(addrinfo.c_str());
        mSrv.sin_family = AF_INET;
#endif
        mSrv.sin_port = htons(port);
        mBase = event_base_new();
        if (!mBase)
        {
            printf("Could not initialize libevent\n");
        }

#ifdef WIN32
        evthread_use_windows_threads();
#else
        evthread_use_pthreads();
#endif
        this->mThread = new thread(ThreadRun,this);
        this->mObserver = p;
        mByteRecv = 0;
        mByteSend = 0;
        this->mStatus = TcpClientLibevent::Status::STOP;
}

int TcpClientLibevent::ConnectServer() {
  printf("server conecting...\r\n");
  if(this->mStatus == TcpClientLibevent::CONNECTED) { 
    return 0;
  }
  if(this->mStatus == TcpClientLibevent::CONNECTING) { 
    return -1;
  }
  evthread_make_base_notifiable(mBase);
  mBev = bufferevent_socket_new(mBase, -1,
    BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE);
  if (nullptr == mBev) {
    this->mStatus = TcpClientLibevent::FAIL;
    return - 1;
  }
  bufferevent_setcb(mBev, &ClientCallback::conn_readcb, 
    &ClientCallback::conn_writecb,
    &ClientCallback::conn_eventcb,
    this);

  int flag = bufferevent_socket_connect(mBev, (struct sockaddr *)&mSrv, sizeof(mSrv));
  bufferevent_enable(mBev, EV_READ | EV_WRITE);
  if (-1 == flag) {
    this->mStatus = TcpClientLibevent::FAIL;
    bufferevent_free(mBev);
    mBev = nullptr;
    printf("Connect failed\n");
    return -1;
  }
  this->mStatus = TcpClientLibevent::CONNECTING;
  bufferevent_setwatermark(mBev,EV_WRITE,10,100);
  return 0;
}

int TcpClientLibevent::ConnectServerSync()
{
  evthread_make_base_notifiable(mBase);
    if (nullptr != mBev) {
        delete mBev;
    }
  mBev = bufferevent_socket_new(mBase, -1,
    BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE);
  if (nullptr == mBev) {
    this->mStatus = TcpClientLibevent::FAIL;
    return -1;
  }
  bufferevent_setcb(mBev, &ClientCallback::conn_readcb, 
    &ClientCallback::conn_writecb,
    &ClientCallback::conn_eventcb,
    this);

  int flag = bufferevent_socket_connect(mBev, (struct sockaddr*)&mSrv, sizeof(mSrv));
  if (-1 == flag) {
    this->mStatus = TcpClientLibevent::FAIL;
    bufferevent_free(mBev);
    mBev = nullptr;
    printf("Connect failed\n");
    return -1;
  }
  bufferevent_enable(mBev, EV_READ | EV_WRITE);
  this->mStatus = TcpClientLibevent::CONNECTING;
  auto start = system_clock::to_time_t(system_clock::now());
  while (this->mStatus != TcpClientLibevent::CONNECTED) {
  auto end = system_clock::to_time_t(system_clock::now());
    if ((end - start) > 5) {
      this->mStatus = TcpClientLibevent::FAIL;
      break;
    }
  }
  return 0;
}

int TcpClientLibevent::SetReconnect(bool reconn) {
  this->mReConnect = reconn;
  return 0;
}

int TcpClientLibevent::SetObserver(TcpClientLibevent::TcpClientObserver *ob) {
  this->mObserver = ob;
  return 0;
}

int TcpClientLibevent::Dispatch() {
  return event_base_dispatch(mBase);; 
}

int TcpClientLibevent::Close() {
  event_base_free(mBase);
  return 0;
}
/*

*/
int TcpClientLibevent::SendDataAsync(const char* data, int len)
{
  if((data == nullptr) || (mStatus != CONNECTED)){
      return -1;
  }
  /* If everything is okay, we need to schedule a write */
  // if (len > 0 && (mBev->enabled & EV_WRITE)) {
  //      event_active(&mBev->ev_write,EV_WRITE,1);
  // }
  int res = 0;
  res = evbuffer_add(bufferevent_get_output(mBev), data, len);   
  if (res == -1){
    std::cout<<"evbuffer_add returns" << res << std::endl;
    return (res);
  }
  std::cout<<"SendDataAsync"<<std::endl;
  return (res);
}


int TcpClientLibevent::SendDataSync(const char* data, int len){
  if(nullptr == data){
      return -1;
  }
  std::lock_guard<std::mutex> lock(mMutex);
  int ret = bufferevent_write(this->mBev,
      data,len);
  std::cout<<"SendDataSync"<<std::endl;
  return ret;
}

uint64_t TcpClientLibevent::SocketFd()
{
  return mSocketFD;
}
